/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.capitalone.dashboard;

import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.admin.SVNAdminClient;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Sandbox {

  public static Sandbox createWithoutCleanup(String testName, TestOptions testOptions) throws SVNException {
    return create(testName, testOptions, false);
  }

  public static Sandbox createWithCleanup(String testName, TestOptions testOptions) throws SVNException {
    return create(testName, testOptions, true);
  }

  private final String testName;

  private final TestOptions testOptions;
  private final List<WorkingCopy> workingCopies;
  private File testDirectory;
  private final List<ApacheProcess> apacheProcesses;
  private final List<SvnserveProcess> svnserveProcesses;
  private final Map<SVNURL, File> urlToRepositoryRoot;

  private Sandbox(String testName, TestOptions testOptions) {
    this.testName = testName;
    this.testOptions = testOptions;
    this.workingCopies = new ArrayList<WorkingCopy>();
    this.apacheProcesses = new ArrayList<ApacheProcess>();
    this.svnserveProcesses = new ArrayList<SvnserveProcess>();
    this.urlToRepositoryRoot = new HashMap<SVNURL, File>();
  }

  public void dispose() {
    for (WorkingCopy workingCopy : workingCopies) {
      workingCopy.dispose();
    }
    for (ApacheProcess apacheProcess : apacheProcesses) {
      ApacheProcess.shutdown(apacheProcess);
    }
    for (SvnserveProcess svnserveProcess : svnserveProcesses) {
      SvnserveProcess.shutdown(svnserveProcess);
    }
  }

  public WorkingCopy checkoutWorkingCopy() throws SVNException {
    return checkoutWorkingCopyOrUpdateTo(SVNRepository.INVALID_REVISION);
  }

  public WorkingCopy checkoutWorkingCopyOrUpdateTo(long revision) throws SVNException {
    if (getWorkingCopyDirectory().exists()) {
      final WorkingCopy workingCopy = openExistingWorkingCopy();
      workingCopy.updateToRevision(revision);
      return workingCopy;
    } else {
      return checkoutOrUpdateExistingWorkingCopy(getRepositoryUrl(), revision);
    }
  }

  private WorkingCopy openExistingWorkingCopy() {
    final WorkingCopy workingCopy = new WorkingCopy(getTestOptions(), getWorkingCopyDirectory());
    workingCopy.setRepositoryUrl(getRepositoryUrl());
    workingCopies.add(workingCopy);
    return workingCopy;
  }

  public WorkingCopy checkoutOrUpdateExistingWorkingCopy(SVNURL repositoryUrl, long revision) throws SVNException {
    return checkoutOrUpdateExistingWorkingCopy(repositoryUrl, revision, SvnWcGeneration.V17);
  }

  public WorkingCopy checkoutOrUpdateExistingWorkingCopy(SVNURL repositoryUrl, long revision, SvnWcGeneration wcGeneration) throws SVNException {
    final WorkingCopy workingCopy = new WorkingCopy(getTestOptions(), getWorkingCopyDirectory());
    workingCopy.setWcGeneration(wcGeneration);
    workingCopy.checkoutRevision(repositoryUrl, revision);
    workingCopies.add(workingCopy);
    return workingCopy;
  }

  public WorkingCopy checkoutNewWorkingCopy(SVNURL url) throws SVNException {
    return checkoutNewWorkingCopy(url, SVNRevision.HEAD.getNumber());
  }

  public WorkingCopy checkoutNewWorkingCopy(SVNURL repositoryUrl, long revision) throws SVNException {
    return checkoutNewWorkingCopy(repositoryUrl, revision, true, TestUtil.getDefaultWcGeneration());
  }

  public WorkingCopy checkoutNewWorkingCopy(SVNURL repositoryUrl, long revision, boolean ignoreExternals, SvnWcGeneration wcGeneration) throws SVNException {
    return checkoutNewWorkingCopy(repositoryUrl, revision, ignoreExternals, wcGeneration, createWorkingCopyDirectory());
  }

  public WorkingCopy checkoutNewWorkingCopy(SVNURL repositoryUrl, long revision, boolean ignoreExternals, SvnWcGeneration wcGeneration, File wcDirectory) throws SVNException {
    final WorkingCopy workingCopy = new WorkingCopy(getTestOptions(), wcDirectory);
    workingCopy.setWcGeneration(wcGeneration);
    workingCopy.checkoutRevision(repositoryUrl, revision, ignoreExternals);
    workingCopies.add(workingCopy);
    return workingCopy;
  }

  public SVNURL getFSFSAccessUrl(SVNURL url) {
    if ("file".equals(url.getProtocol())) {
      return url;
    }
    final File repositoryRoot = urlToRepositoryRoot.get(url);
    try {
      return SVNURL.fromFile(repositoryRoot);
    } catch (SVNException e) {
      return null;
    }
  }

  private SVNURL getRepositoryUrl() {
    final SVNURL repositoryUrl = getTestOptions().getRepositoryUrl();

    if (repositoryUrl == null) {
      throw new RuntimeException("Unable to start the test: repository URL is not specified.");
    }

    return repositoryUrl;
  }

  private String getTestName() {
    return testName;
  }

  private File getTestDirectory() {
    if (testDirectory == null) {
      testDirectory = createTestDirectory();
    }
    return testDirectory;
  }

  public SVNURL createSvnRepository() throws SVNException {
    final File repositoryDirectory = createDirectory("svn.repo");
    createSvnRepository(repositoryDirectory);
    SVNURL url = SVNURL.fromFile(repositoryDirectory);
    urlToRepositoryRoot.put(url, repositoryDirectory);
    return url;
  }

  public SVNURL createSvnRepositoryWithDavAccess() throws SVNException {
    return createSvnRepositoryWithDavAccess(null);
  }

  public SVNURL createSvnRepositoryWithSvnAccess() throws SVNException {
    return createSvnRepositoryWithSvnAccess(null);
  }

  public SVNURL createSvnRepositoryWithDavAccess(Map<String, String> loginToPassword) throws SVNException {
    final File repositoryDirectory = createDirectory("svn.repo");
    createSvnRepository(repositoryDirectory);
    final ApacheProcess apacheProcess = runApacheForSvnRepository(repositoryDirectory, loginToPassword);
    SVNURL url = apacheProcess.getUrl();
    urlToRepositoryRoot.put(url, repositoryDirectory);
    return url;
  }

  public SVNURL createSvnRepositoryWithSvnAccess(Map<String, String> loginToPassword) throws SVNException {
    final File repositoryDirectory = createDirectory("svn.repo");
    createSvnRepository(repositoryDirectory);
    final SvnserveProcess svnserveProcess = runSvnserveForSvnRepository(repositoryDirectory, loginToPassword);
    SVNURL url = svnserveProcess.getUrl();
    urlToRepositoryRoot.put(url, repositoryDirectory);
    return url;
  }

  public File createFailingHook(SVNURL url, String hookName) throws SVNException {
    final String failingHookContents = TestUtil.getFailingHookContents();
    return createHook(url, hookName, failingHookContents);
  }

  public File createHook(SVNURL url, String hookName, String failingHookContents) throws SVNException {
    final File repositoryRoot = urlToRepositoryRoot.get(url);
    final File hookFile = TestUtil.getHookFile(repositoryRoot, hookName);
    TestUtil.writeFileContentsString(hookFile, failingHookContents);
    SVNFileUtil.setExecutable(hookFile, true);
    return hookFile;
  }

  public File writeActiveAuthzContents(SVNURL url, String contents) throws SVNException {
    //maybe url is served by apache?
    final ApacheProcess apacheProcess = findApacheProcess(url);
    if (apacheProcess != null) {

      final File activeAuthzFile = apacheProcess.getAuthzFile();
      if (activeAuthzFile == null) {
        return null;
      }
      TestUtil.writeFileContentsString(activeAuthzFile, contents);
      apacheProcess.reload(); //reload apache configuration
      return activeAuthzFile;
    }

    //maybe url is served by svnserve?
    final SvnserveProcess svnserveProcess = findSvnserveProcess(url);
    if (svnserveProcess != null) {

      final File activeAuthzFile = svnserveProcess.getAuthzFile();
      if (activeAuthzFile == null) {
        return null;
      }

      TestUtil.writeFileContentsString(activeAuthzFile, contents);
      svnserveProcess.reload();
      return activeAuthzFile;
    }

    //authz for FSFS is useless
    return null;
  }

  public File writeHookContents(SVNURL url, String hookName, String hookContents) throws SVNException {
    final File repositoryRoot = urlToRepositoryRoot.get(url);

    final File hookFile = TestUtil.getHookFile(repositoryRoot, hookName);
    TestUtil.writeFileContentsString(hookFile, hookContents);

    SVNFileUtil.setExecutable(hookFile, true);

    return hookFile;
  }

  private ApacheProcess findApacheProcess(SVNURL url) {
    for (ApacheProcess apacheProcess : apacheProcesses) {
      if (apacheProcess.getUrl().equals(url)) {
        return apacheProcess;
      }
    }
    return null;
  }

  private SvnserveProcess findSvnserveProcess(SVNURL url) {
    for (SvnserveProcess svnserveProcess : svnserveProcesses) {
      if (svnserveProcess.getUrl().equals(url)) {
        return svnserveProcess;
      }
    }
    return null;
  }

  private File getWorkingCopyDirectory() {
    return new File(getTestDirectory(), "wc");
  }

  private File createWorkingCopyDirectory() {
    return createDirectory("wc");
  }

  public File createDirectory(String suggestedName) {
    return TestUtil.createDirectory(getTestDirectory(), suggestedName).getAbsoluteFile();
  }

  private File createTestDirectory() {
    final File testDirectory = new File(getTempDirectory(), getTestName());
    testDirectory.mkdirs();
    return testDirectory;
  }

  private TestOptions getTestOptions() {
    return testOptions;
  }

  private File getTempDirectory() {
    return getTestOptions().getTempDirectory();
  }

  private void cleanup() throws SVNException {
    SVNFileUtil.deleteAll(getTestDirectory(), null);
  }

  private static Sandbox create(String testName, TestOptions testOptions, boolean cleanup) throws SVNException {
    final Sandbox sandbox = new Sandbox(testName, testOptions);
    if (cleanup) {
      sandbox.cleanup();
    }
    return sandbox;
  }

  private ApacheProcess runApacheForSvnRepository(File repositoryDirectory, Map<String, String> loginToPassword) throws SVNException {
    final ApacheProcess apacheProcess = ApacheProcess.run(getTestOptions(), repositoryDirectory, loginToPassword);
    apacheProcesses.add(apacheProcess);
    return apacheProcess;
  }

  private SvnserveProcess runSvnserveForSvnRepository(File repositoryDirectory, Map<String, String> loginToPassword) throws SVNException {
    final SvnserveProcess svnserveProcess = SvnserveProcess.run(getTestOptions(), repositoryDirectory, loginToPassword);
    svnserveProcesses.add(svnserveProcess);
    return svnserveProcess;
  }

  private void createSvnRepository(File repositoryDirectory) throws SVNException {
    final SVNClientManager clientManager = SVNClientManager.newInstance();
    try {
      SVNAdminClient adminClient = clientManager.getAdminClient();
      adminClient.doCreateRepository(repositoryDirectory, null, true, false);

    } finally {
      clientManager.dispose();
    }
  }
}
